45  Python内置函数

本章定位(复习与补充训练层):本章对应正课第4章《常用内置函数》(章节 4),用于复习、补缺与额外练习。建议先不看讲解,直接尝试下方平台任务,再对照解析补弱项。本章不属于必修主线。先做本章『动手与思考』第 1 题与平台任务自测,通过即可跳过本章。

45.1 引言Python内置函数的设计哲学

内置函数无需导入任何模块即可使用,是Python编程的基础工具箱:数学统计、序列操作、迭代器与类型转换一应俱全。本章用一个含五个小任务的平台代码块,检验你对常用内置函数的复习效果。

45.2 本章学习目标

本章为复习与补充训练,目标是检验你是否能脱离正课独立完成下列任务:

  1. 能独立完成平台任务一:用 len() 统计券商列表元素个数,用 list(enumerate(stock, start=1)) 输出带序号的排名列表
  2. 能独立完成平台任务二:用 sum() 求十家券商净利润总和,配合 len()round(x, 4) 计算并输出平均值
  3. 能独立完成平台任务三:用 max()min() 找出一季度股价的最大/最小涨幅,用 sorted() 对股价升序排列
  4. 能独立完成平台任务四:用 zip() 把名称、代码、净利润、涨幅、股价五个平行列表打包为元组列表;任务五:用 int() 对整手买入的股数取整
  5. 能说出 map()filter() 的作用,以及 round() 的”银行家舍入”行为

若上述任务无法独立完成,请回到正课第4章对应小节补弱,再回到本章练习。

45.3 常用内置函数

平台任务(平台原始代码)

以下代码与教学平台任务要求完全一致:

任务要求(单块含任务一至任务五):给定2023年净利润排名前10位证券公司的相关数据列表。 - 任务一:用 len() 统计券商列表元素个数,用 list(enumerate(stock, start=1)) 输出带序号的排名列表; - 任务二:用 sum() 求十家券商净利润总和,配合 len()round(x, 4) 计算并输出平均值; - 任务三:用 max()min() 找出一季度股价的最大/最小涨幅,用 sorted() 对股价升序排列; - 任务四:用 zip() 把名称、代码、净利润、涨幅、股价五个平行列表打包为元组列表输出; - 任务五:用 int() 对”整手买入”的股数取整并输出。 请按平台原始题面原样输入(注释除外),判定以平台为准。

列表 45.1: 平台原始代码 (1)-(5)
# 注:平台题面为填空形式,下方展示的是填空完成后的参考形态;请按平台原始题面原样输入,以平台判定为准
# ⚠️ 平台原始代码 - 请原样输入至教学平台(注释除外),平台才会判定答案正确
#任务一
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]
print(len(stock)) #计算列表中元素的个数
print(list(enumerate(stock,start=1))) #创建带有索引并且以列表方式分输出


#任务二
profit = [205.39,130.36,98.85,87.69,78.84,78.63,70.47,64.27,61.64,54.75]
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]  # 定义列表stock
profit_total = sum(profit)  #计算10家证券公司的净利润总和
profit_average = profit_total/len(stock)  # 获取数据长度

print("2023年净利润排名前10位的证券公司净利润总和(亿元)",profit_total)  # 输出2023年净利润排名前10位的证券公司净利润总和(
print("2023年净利润排名前10位的证券公司净利润平均数(亿元)",round(profit_average,4))  # 输出2023年净利润排名前10位的证券公司净利润平均数

#任务三
return_Q1 = [-0.0588,-0.0067,-0.0698,0.0239,-0.0059,-0.0672,-0.0739,-0.0193,-0.1543,0.0046]
return_max = max(return_Q1) #找出最大涨幅
return_min = min(return_Q1) #找出最小涨幅
print("2024年1季度股价的最大涨幅",return_max)  # 输出2024年1季度股价的最大涨幅
print("2024年1季度股价的最小涨幅",return_min)  # 输出2024年1季度股价的最小涨幅

price = [18.73,13.61,13.47,13.71,11.76,13.05,21.68,8.11,32.03,4.40]  # 定义列表price
price_sorted = sorted(price) #将股价由小到大排序
print(price_sorted)  # 输出价格数据

#任务四
stock = ["中信证券","华泰证券","国泰君安","招商证券","中国银河","广发证券","中信建投","国信证券","中金公司","申万宏源"]
# 定义列表code
code = ["600030","601688","601211","600999","601881","000776","601066","002736","601995","000166"]
profit = [205.39,130.36,98.85,87.69,78.84,78.63,70.47,64.27,61.64,54.75]  # 定义列表profit
# 定义列表return_Q1
return_Q1 = [-0.0588,-0.0067,-0.0698,0.0239,-0.0059,-0.0672,-0.0739,-0.0193,-0.1543,0.0046]
price = [18.73,13.61,13.47,13.71,11.76,13.05,21.68,8.11,32.03,4.40]  # 定义列表price
print(list(zip(stock,code,profit,return_Q1,price))) #将多个列表中对应的元素打包为一个元组并以列表方式输出

#任务五
fund = 1e7 #H公司的投资资金额
price_huatai = 13.76 #华泰证券股票
N = 100  # 设置期数/数量为100
share = N*int(fund/(price_huatai*N))  # 将数据转换为整数

print("H公司购买华泰证券的股票数量",share)  # 输出H公司购买华泰证券的股票数量

预期输出(本机实际运行结果,具体以平台运行结果为准):

10
[(1, '中信证券'), (2, '华泰证券'), (3, '国泰君安'), (4, '招商证券'), (5, '中国银河'), (6, '广发证券'), (7, '中信建投'), (8, '国信证券'), (9, '中金公司'), (10, '申万宏源')]
2023年净利润排名前10位的证券公司净利润总和(亿元) 930.89
2023年净利润排名前10位的证券公司净利润平均数(亿元) 93.089
2024年1季度股价的最大涨幅 0.0239
2024年1季度股价的最小涨幅 -0.1543
[4.4, 8.11, 11.76, 13.05, 13.47, 13.61, 13.71, 18.73, 21.68, 32.03]
[('中信证券', '600030', 205.39, -0.0588, 18.73), ('华泰证券', '601688', 130.36, -0.0067, 13.61), ('国泰君安', '601211', 98.85, -0.0698, 13.47), ('招商证券', '600999', 87.69, 0.0239, 13.71), ('中国银河', '601881', 78.84, -0.0059, 11.76), ('广发证券', '000776', 78.63, -0.0672, 13.05), ('中信建投', '601066', 70.47, -0.0739, 21.68), ('国信证券', '002736', 64.27, -0.0193, 8.11), ('中金公司', '601995', 61.64, -0.1543, 32.03), ('申万宏源', '000166', 54.75, 0.0046, 4.4)]
H公司购买华泰证券的股票数量 726700
列表 45.2: 常用内置函数示例与金融数据计算
# 创建数值列表,用于演示各种内置函数
numbers = [1, 2, 3, 4, 5]

# 1. len(): 获取序列长度
# 返回序列中元素的个数
# 适用于字符串、列表、元组、字典等
print(f'长度: {len(numbers)}')
# 输出: 5

# 2. max(): 返回最大值
# 对数值序列,返回最大的数
# 对字符串序列,按Unicode编码比较
print(f'最大值: {max(numbers)}')
# 输出: 5

# 3. min(): 返回最小值
# 与max()类似,返回最小的元素
print(f'最小值: {min(numbers)}')
# 输出: 1

# 4. sum(): 计算序列和
# 从左到右对元素求和
# 注意:sum()只支持数值类型
print(f'求和: {sum(numbers)}')
# 输出: 15 (1+2+3+4+5)

# 5. sorted(): 返回排序后的新列表
# 原序列保持不变
# 默认升序,reverse=True可降序
print(f'排序: {sorted(numbers, reverse=True)}')
# 输出: [5, 4, 3, 2, 1]

# 6. abs(): 返回绝对值
# 对整数或浮点数取绝对值
# 对复数,返回模长
print(f'绝对值: {abs(-10)}')
# 输出: 10

# 7. round(): 四舍五入
# 语法: round(number, ndigits)
# ndigits指定小数位数,默认为0(取整)
print(f'四舍五入: {round(3.14159, 2)}')
# 输出: 3.14

# 8. pow(): 幂运算
# 等价于 x ** y,但支持模运算
# pow(x, y, mod) 等价于 (x ** y) % mod
print(f'幂运算: {pow(2, 3)}')
# 输出: 8

代码深度解析:

  1. len()的时间复杂度:

    # Python对象内部维护长度信息
    # 因此len()的时间复杂度是O(1)
    
    # 列表
    lst = [1, 2, 3]
    # 内部结构: PyObject_VAR_HEAD包含ob_size字段
    print(len(lst))  # 直接读取ob_size,无需遍历
    
    # 字符串
    s = "hello"
    # 同样直接读取长度信息
    print(len(s))  # O(1)
    
    # 但对于生成器等迭代器,len()不适用
    # 需要转换为列表或手动计数
  2. max()/min()的多键排序:

    # 复杂结构的多键比较
    portfolio = [
        {'name': '茅台', 'price': 1850, 'pe': 45},
        {'name': '五粮液', 'price': 220, 'pe': 32},
        {'name': '招商银行', 'price': 45, 'pe': 8}
    ]
    
    # 找出PE最高的股票
    # key参数指定比较依据
    highest_pe = max(portfolio, key=lambda x: x['pe'])
    print(f'PE最高: {highest_pe["name"]}, PE={highest_pe["pe"]}')
    
    # 找出价格最低的股票
    lowest_price = min(portfolio, key=lambda x: x['price'])
    print(f'价格最低: {lowest_price["name"]}, 价格={lowest_price["price"]}')
  3. sorted()的稳定性:

    # Python的排序是稳定的(stable)
    # 即相等元素的相对顺序保持不变
    
    # 先按价格排序
    stocks = [
        ('茅台', 1850, '沪'),
        ('五粮液', 220, '深'),
        ('平安', 45, '深'),
        ('招行', 45, '沪')
    ]
    
    # 按价格升序,价格相同时保持原顺序
    sorted_by_price = sorted(stocks, key=lambda x: x[1])
    print(sorted_by_price)
    # ('平安', 45, '深') 在 ('招行', 45, '沪') 之前
  4. 金融应用:计算统计量:

    # 收益率序列
    returns = [0.05, -0.02, 0.03, 0.07, -0.01]
    
    # 平均收益率
    avg = sum(returns) / len(returns)
    print(f'平均收益率: {avg:.2%}')
    
    # 最大收益和最大亏损
    max_return = max(returns)
    min_return = min(returns)
    print(f'最大收益: {max_return:.2%}')
    print(f'最大亏损: {min_return:.2%}')
    
    # 极差(Range)
    range_ = max(returns) - min(returns)
    print(f'收益极差: {range_:.2%}')
  5. round()的银行家舍入:

    # Python 3使用"银行家舍入"(Round Half to Even)
    # 当正好为0.5时,舍入到最近的偶数
    
    print(round(2.5))  # 2 (最近的偶数)
    print(round(3.5))  # 4 (最近的偶数)
    
    # 这不同于传统的"四舍五入"
    # 原因是减少统计偏差
    
    # 传统四舍五入需要使用decimal模块
    from decimal import Decimal, ROUND_HALF_UP
    print(Decimal('2.5').quantize(Decimal('1'), rounding=ROUND_HALF_UP))  # 3

最后从设计层面回顾内置函数:它们体现了语言的实用性(Pragmatism)哲学,并按功能形成清晰的类别划分。

理论背景:函数式编程与内置函数

Python的内置函数(Built-in Functions)体现了语言的实用性(Pragmatism)设计哲学。这些函数无需导入任何模块即可使用,它们构成了Python编程的基础工具箱。从计算机科学的角度,内置函数的设计遵循以下原则:

  1. 高层抽象: 隐藏底层实现细节,提供简洁接口
  2. 泛型设计: 同一函数可应用于多种数据类型
  3. 性能优化: 用C语言实现,比纯Python代码快10-100倍
  4. 一致性: 遵循统一的命名和调用约定

数学分类:内置函数的功能划分

按功能可将内置函数分为以下类别:

类别 代表函数 数量 金融应用场景
数学运算 abs(), round(), sum() 8 收益计算,四舍五入
序列操作 len(), max(), min(), sorted() 12 价格排序,极值查找
迭代器 map(), filter(), zip() 7 数据转换,批量处理
类型转换 int(), float(), str() 15 数据清洗,类型转换
对象操作 isinstance(), id(), hash() 10 类型检查,内存管理
输入输出 print(), input(), open() 5 数据读取,结果输出

补充说明:为什么内置函数用C实现?

sum()函数为例:

# 纯Python实现(慢)
def sum_python(iterable):
    total = 0
    for item in iterable:
        total += item
    return total

# C实现(快)
# 省去了Python字节码解释的开销
# 直接在C层面循环,速度提升10-50倍

性能对比测试(求和100万个整数):

  • sum_python(): 约50ms
  • sum(): 约5ms
  • 性能提升: 10倍

45.4 高级内置函数

列表 45.3: 高级内置函数map()、filter()、zip()
# 1. map(): 应用函数到可迭代对象的每个元素
# 语法: map(function, iterable, ...)
# 返回一个迭代器,需要用list()转换为列表
prices = ['10.5', '20.3', '15.8']

# 将字符串列表转换为浮点数列表
# float()是内置函数,将字符串转换为浮点数
prices_float = list(map(float, prices))
print(f'转换为浮点数: {prices_float}')
# 输出: [10.5, 20.3, 15.8]

# 2. filter(): 过滤元素
# 语法: filter(function, iterable)
# function返回True时保留元素,False时过滤掉
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# 使用lambda表达式定义过滤条件
# lambda x: x % 2 == 0 表示"偶数"
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(f'偶数: {evens}')
# 输出: [2, 4, 6, 8, 10]

# 3. zip(): 配对多个可迭代对象
# 将多个序列对应位置的元素组合成元组
names = ['张三', '李四', '王五']
ages = [25, 30, 35]

# zip()返回迭代器,每个元素是一个元组
pairs = list(zip(names, ages))
print(f'配对: {pairs}')
# 输出: [('张三', 25), ('李四', 30), ('王五', 35)]

# zip()的妙用:字典构造
# 直接将两个列表转换为字典
name_age_dict = dict(zip(names, ages))
print(f'字典: {name_age_dict}')
# 输出: {'张三': 25, '李四': 30, '王五': 35}

# 4. enumerate(): 同时获取索引和元素
# 语法: enumerate(iterable, start=0)
# 返回(索引, 元素)的迭代器
stocks = ['茅台', '五粮液', '招行']

for index, stock in enumerate(stocks, start=1):
    print(f'{index}. {stock}')
# 输出:
# 1. 茅台
# 2. 五粮液
# 3. 招行

代码深度解析:

  1. map()的等价实现:

    # map()的纯Python实现
    def map_python(func, iterable):
        result = []
        for item in iterable:
            result.append(func(item))
        return result
    
    # 使用示例
    numbers = [1, 2, 3, 4, 5]
    squared = list(map(lambda x: x**2, numbers))
    print(squared)  # [1, 4, 9, 16, 25]
    
    # 等价于列表推导式
    squared = [x**2 for x in numbers]
    print(squared)  # [1, 4, 9, 16, 25]
    
    # 列表推导式通常更Pythonic且更易读
  2. filter()的等价实现:

    # filter()的纯Python实现
    def filter_python(func, iterable):
        result = []
        for item in iterable:
            if func(item):  # 如果函数返回True
                result.append(item)
        return result
    
    # 使用示例
    numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    # 过滤出大于5的数
    greater_than_5 = list(filter(lambda x: x > 5, numbers))
    print(greater_than_5)  # [6, 7, 8, 9, 10]
    
    # 等价于列表推导式
    greater_than_5 = [x for x in numbers if x > 5]
    print(greater_than_5)  # [6, 7, 8, 9, 10]
  3. zip()的配对规则:

    # zip()以最短的序列为准
    list1 = [1, 2, 3, 4, 5]
    list2 = ['a', 'b', 'c']
    
    # 只配对前3个元素
    zipped = list(zip(list1, list2))
    print(zipped)  # [(1, 'a'), (2, 'b'), (3, 'c')]
    
    # zip(*)的反向操作:解包
    matrix = [(1, 'a'), (2, 'b'), (3, 'c')]
    numbers, letters = zip(*matrix)
    print(numbers)  # (1, 2, 3)
    print(letters)  # ('a', 'b', 'c')
  4. 函数式编程应用:

    # 组合使用map()、filter()、reduce()
    
    from functools import reduce  # reduce在Python 3中移到functools
    
    # 场景:计算股票组合的加权收益率
    stocks = [
        {'name': '茅台', 'return': 0.05, 'weight': 0.4},
        {'name': '五粮液', 'return': 0.03, 'weight': 0.3},
        {'name': '招行', 'return': 0.02, 'weight': 0.3}
    ]
    
    # 1. 提取收益率
    returns = list(map(lambda x: x['return'], stocks))
    
    # 2. 提取权重
    weights = list(map(lambda x: x['weight'], stocks))
    
    # 3. 计算加权收益率
    # reduce()对序列进行累积计算
    weighted_return = sum(r * w for r, w in zip(returns, weights))
    print(f'加权收益率: {weighted_return:.2%}')
  5. 金融应用:数据清洗流水线:

    # 原始数据:字符串格式的价格和数量
    raw_data = [
        ('10.5', '100'),
        ('20.3', '200'),
        ('15.8', '150')
    ]
    
    # 使用map()进行数据转换
    prices = list(map(lambda x: float(x[0]), raw_data))
    quantities = list(map(lambda x: int(x[1]), raw_data))
    
    # 计算每笔交易金额
    amounts = list(map(lambda p, q: p * q, prices, quantities))
    print(f'交易金额: {amounts}')
    # [1050.0, 4060.0, 2370.0]
    
    # 过滤出金额大于2000的交易
    large_trades = list(filter(lambda x: x > 2000, amounts))
    print(f'大额交易: {large_trades}')
    # [4060.0, 2370.0]

45.5 金融应用实例投资组合分析

列表 45.4: 内置函数在金融计算中的应用
# 定义投资组合的日收益率序列
# 正值表示上涨,负值表示下跌
returns = [0.05, -0.02, 0.03, 0.07, -0.01]

# 1. 基础统计:平均收益率
# sum()计算总和,len()获取个数
avg_return = sum(returns) / len(returns)
print(f'平均收益率: {avg_return:.2%}')
# 输出: 平均收益率: 2.40%

# 2. 统计正收益天数
# 使用列表推导式过滤正收益
# len()统计数量
positive_returns = [r for r in returns if r > 0]
positive_count = len(positive_returns)
print(f'正收益次数: {positive_count}/{len(returns)}')
# 输出: 正收益次数: 3/5

# 3. 计算最大回撤
# 最大回撤是从峰值到谷底的最大跌幅
# 这是一个重要的风险指标

cum_return = 1.0  # 初始累计收益
max_drawdown = 0   # 最大回撤
peak = 1.0        # 历史峰值

# 遍历每日收益率
for r in returns:
    # 更新累计收益
    # cum_return *= (1 + r) 等价于 cum_return = cum_return * (1 + r)
    cum_return *= (1 + r)

    # 更新历史峰值
    # 如果当前收益超过历史峰值,更新峰值
    if cum_return > peak:
        peak = cum_return

    # 计算当前回撤
    # 回撤 = (峰值 - 当前值) / 峰值
    drawdown = (peak - cum_return) / peak

    # 更新最大回撤
    # max()函数返回两个值中的较大者
    max_drawdown = max(max_drawdown, drawdown)

print(f'最大回撤: {max_drawdown:.2%}')
# 输出: 最大回撤: 2.00%

# 4. 计算夏普比率(简化版)
# 夏普比率衡量单位风险的超额收益
# Sharpe = (平均收益率 - 无风险利率) / 标准差

# 首先计算标准差
import math  # 导入math模块用于sqrt()

# 计算方差
mean = sum(returns) / len(returns)
variance = sum((r - mean) ** 2 for r in returns) / len(returns)

# 标准差是方差的平方根
std_dev = math.sqrt(variance)

# 假设无风险利率为2%(年化)
risk_free_rate = 0.02

# 计算夏普比率(年化)
# 假设252个交易日
sharpe_ratio = (mean * 252 - risk_free_rate) / (std_dev * math.sqrt(252))

print(f'夏普比率: {sharpe_ratio:.2f}')
# 输出某个数值

# 5. 计算收益率的分位数
# 使用sorted()和索引计算
sorted_returns = sorted(returns)
n = len(sorted_returns)

# 中位数(50%分位数)
if n % 2 == 0:
    median = (sorted_returns[n//2 - 1] + sorted_returns[n//2]) / 2
else:
    median = sorted_returns[n//2]

print(f'中位数收益率: {median:.2%}')

# 25%分位数(下四分位数)
q25_index = int(n * 0.25)
q25 = sorted_returns[q25_index]
print(f'25%分位数: {q25:.2%}')

# 75%分位数(上四分位数)
q75_index = int(n * 0.75)
q75 = sorted_returns[q75_index]
print(f'75%分位数: {q75:.2%}')

代码深度解析:

  1. 最大回撤的计算原理:

    # 最大回撤是评估策略风险的关键指标
    # 它回答了"从最高点到最低点,最多亏损多少?"
    
    # 可视化理解
    # 假设累计收益曲线如下:
    # 1.0 -> 1.05 -> 1.029 -> 1.0599 -> 1.1341 -> 1.1228
    #        ↑峰值   ↓回撤2%    ↑新峰值   ↑新峰值  ↓回撤
    
    # 计算步骤:
    # 1. 初始峰值 = 1.0
    # 2. 第1天: 1.0 * 1.05 = 1.05, 新峰值 = 1.05
    # 3. 第2天: 1.05 * 0.98 = 1.029, 回撤 = (1.05-1.029)/1.05 ≈ 2%
    # 4. 第3天: 1.029 * 1.03 = 1.0599, 新峰值 = 1.0599
    # 5. 第4天: 1.0599 * 1.07 = 1.1341, 新峰值 = 1.1341
    # 6. 第5天: 1.1341 * 0.99 = 1.1228, 回撤 = (1.1341-1.1228)/1.1341 ≈ 1%
    
    # 最终最大回撤 = max(2%, 1%) = 2%
  2. 标准差的数学含义:

    # 标准差衡量数据的离散程度
    # 在金融中,标准差代表波动率,是风险的度量
    
    # 计算公式:
    # σ = sqrt(Σ(xi - μ)² / n)
    
    # 其中:
    # - xi: 第i个收益率
    # - μ: 平均收益率
    # - n: 样本数量
    
    # 手动计算示例
    returns = [0.05, -0.02, 0.03, 0.07, -0.01]
    mean = sum(returns) / len(returns)  # 0.024
    
    # 计算每个收益率与均值的偏差平方
    squared_deviations = [(r - mean) ** 2 for r in returns]
    
    # 方差是偏差平方的平均
    variance = sum(squared_deviations) / len(returns)
    
    # 标准差是方差的平方根
    std_dev = variance ** 0.5
    print(f'标准差(波动率): {std_dev:.4f}')
  3. 分位数的应用:

    # 分位数描述数据的分布特征
    
    # 在投资组合中的应用
    # 假设有100只股票的收益率
    import random
    random.seed(42)  # 固定随机种子,确保示例结果可复现
    stock_returns = [random.uniform(-0.1, 0.2) for _ in range(100)]
    
    # 找出表现最好的10%股票
    sorted_returns = sorted(stock_returns, reverse=True)
    top_10_percent = sorted_returns[:10]
    
    print(f'前10%股票的平均收益率: {sum(top_10_percent)/len(top_10_percent):.2%}')
    
    # 找出表现最差的10%股票
    bottom_10_percent = sorted_returns[-10:]
    print(f'后10%股票的平均收益率: {sum(bottom_10_percent)/len(bottom_10_percent):.2%}')
  4. 性能优化:使用NumPy替代内置函数:

    # 对于大规模数值计算,NumPy比内置函数快得多
    
    import numpy as np
    import time
    
    # 生成大规模数据
    random.seed(42)  # 固定随机种子,确保示例数据可复现
    large_returns = [random.random() for _ in range(1000000)]
    
    # 方法1:使用内置函数
    start = time.time()
    mean_python = sum(large_returns) / len(large_returns)
    std_python = (sum((x - mean_python) ** 2 for x in large_returns) / len(large_returns)) ** 0.5
    print(f'Python方法: {time.time() - start:.4f}秒')
    
    # 方法2:使用NumPy
    start = time.time()
    returns_array = np.array(large_returns)
    mean_numpy = returns_array.mean()
    std_numpy = returns_array.std()
    print(f'NumPy方法: {time.time() - start:.4f}秒')
    
    # NumPy通常快10-100倍
  5. all()和any()函数的应用:

    # all():所有元素为True时返回True
    # any():任一元素为True时返回True
    
    # 应用1:检查所有收益率是否为正
    returns = [0.01, 0.02, 0.03, -0.01, 0.02]
    all_positive = all(r > 0 for r in returns)
    print(f'所有收益为正: {all_positive}')  # False
    
    # 应用2:检查是否有任何收益率为负
    any_negative = any(r < 0 for r in returns)
    print(f'存在负收益: {any_negative}')  # True
    
    # 应用3:风险控制
    # 检查是否所有持仓的权重之和为1
    weights = [0.3, 0.4, 0.3]
    weights_sum = sum(weights)
    print(f'权重归一化: {abs(weights_sum - 1.0) < 1e-10}')  # True

最佳实践总结:

  1. 优先使用内置函数而非自己实现:

    • 内置函数用C实现,性能更好
    • 代码更简洁,可读性更高
    • 经过充分测试,可靠性更强
  2. 函数式编程 vs 列表推导式:

    # map/filter:函数式风格
    result = list(map(lambda x: x**2, numbers))
    
    # 列表推导式:更Pythonic
    result = [x**2 for x in numbers]
    
    # 推荐:列表表导式更易读
  3. 性能优化建议:

    • 大规模数值计算 → 使用NumPy
    • 复杂逻辑判断 → 使用列表推导式或生成器
    • 需要延迟计算 → 使用迭代器而非列表
  4. 常见陷阱:

    # 陷阱1:修改正在遍历的列表
    lst = [1, 2, 3, 4, 5]
    for item in lst:
        if item % 2 == 0:
            lst.remove(item)  # 危险!可能跳过元素
    
    # 正确做法:创建新列表
    lst = [1, 2, 3, 4, 5]
    lst = [x for x in lst if x % 2 != 0]
    
    # 陷阱2:在可变默认参数中使用mutable对象
    def foo(lst=[]):  # 危险!
        lst.append(1)
        return lst
    
    # 正确做法
    def foo(lst=None):
        if lst is None:
            lst = []
        lst.append(1)
        return lst

45.6 延伸阅读(选读)

历史背景:内置函数的演进

Python的内置函数集合随着语言发展不断丰富:

  • Python 1.x (1991-2000): 约50个内置函数,强调基础操作
  • Python 2.x (2000-2010): 增加到约70个,引入enumerate(), sorted()
  • Python 3.x (2008-至今): 约80个,增加all(), any(), ascii()

45.7 本章小结

要点:

  • 数学与统计:len()(O(1))、sum()max()/min()(可配 key=)、abs()round()pow()
  • 序列工具:sorted() 返回新列表、enumerate() 附带索引、zip() 按位置配对多个序列
  • 类型转换:int() 截断取整、float()str();平台任务五用 int(fund/(price*N))*N 实现”按整手买入”的股数取整
  • map()filter() 返回迭代器,常配 list() 查看;二者与列表推导式可互相改写
  • round() 采用银行家舍入:恰为 .5 时取最近的偶数,如 round(2.5) 得 2

易错点:

  • zip() 以最短序列为准,两列表长度不齐时会悄悄丢弃多出的数据
  • round(2.5) 的结果是 2 不是 3(银行家舍入),与”四舍五入”的直觉不同
  • map()filter() 的结果是一次性迭代器,遍历过后再次使用为空,需要保留就先 list()
  • enumerate(stock) 不写 start=1 时序号从 0 开始

45.8 动手与思考

以下练习每题附参考答案(默认折叠)。请先独立完成并写下你的判断,再点开对照,最后上机验证。

  1. 概念自测(不看正课):len() 为什么是 O(1)?sorted()list.sort() 有何区别?两个长度不等的列表交给 zip() 会发生什么?

    参考答案(点开前请先独立完成)

    解题思路len() 是 O(1) 因为 Python 列表对象内部维护着元素计数字段,取长度只是读这个字段、无需逐个数元素。sorted() 是内置函数,接收任意可迭代对象、返回排序后的新列表、原对象不动;list.sort() 是列表方法,原地排序、返回 None——需要保留原序用前者,只求排好序的结果用后者。两个长度不等的列表交给 zip() 会按较短的一方截断(“木桶原理”),多出的元素被静默丢弃,配对前应先确认长度一致。

    回扣主线章:这些内置函数的系统讲解见第 章节 4 章。

  2. 输出预测:不运行代码,先写出下面代码的输出结果,再上机检验你的判断。

    profit = [205.39, 130.36, 98.85, 87.69]
    print(len(profit))
    print(round(sum(profit) / len(profit), 2))
    print(max(profit), min(profit))
    print(list(enumerate(['中信证券', '华泰证券'], start=1)))
    print(list(zip([1, 2, 3], ['x', 'y'])))
    print(round(2.5))

    参考答案(先写下你的预测再点开)

    解题思路len(profit) 输出 4sum(profit) 为 522.29,除以 4 得 130.5725,round(..., 2) 输出 130.57maxmin 输出 205.39 87.69enumerate(..., start=1) 从 1 起编号,list() 展开后输出 [(1, '中信证券'), (2, '华泰证券')]zip 遇到不等长列表按短的截断,输出 [(1, 'x'), (2, 'y')](元素 3 被丢弃);round(2.5) 输出 2 而非 3——Python 采用“银行家舍入”(四舍六入五取偶),恰为 .5 时取偶数,故 round(3.5) 是 4、round(2.5) 是 2。

    # 验证脚本:原样运行题面代码,并补验round(3.5)
    profit = [205.39, 130.36, 98.85, 87.69]  # 四家券商净利润(亿元)
    print(len(profit))  # 元素个数
    print(round(sum(profit) / len(profit), 2))  # 平均值保留两位小数
    print(max(profit), min(profit))  # 最大值与最小值
    print(list(enumerate(['中信证券', '华泰证券'], start=1)))  # 从1起编号配对
    print(list(zip([1, 2, 3], ['x', 'y'])))  # 不等长按短的截断
    print(round(2.5))  # 银行家舍入:取偶数
    print(round(3.5))  # 对照:同样取偶数,得4

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    4
    130.57
    205.39 87.69
    [(1, '中信证券'), (2, '华泰证券')]
    [(1, 'x'), (2, 'y')]
    2
    4

    回扣主线章round() 的银行家舍入与其余内置函数的讲解见第 章节 4 章。

  3. 动手验证:用 int(1e7 / (13.76 * 100)) * 100 复算平台任务五的整手买入股数;再把 int() 换成 round(),先验证本例中两者结果是否相同,再说明两者在什么情况下(商的小数部分 ≥0.5 时)才会给出不同结果,并用一个自选价格验证 round() 版本可能出现买超资金的情况。

    参考答案(点开前请先独立完成)

    解题思路1e7 / (13.76 × 100) = 7267.44…int() 截断小数得 7267、再乘 100 得 726700 股;round() 四舍五入同样得 7267,故本例两者同为 726700 股——差异只在商的小数部分 ≥0.5 时出现(int 向下截断、round 向上进位)。用自选价格 13.75 元验证:商为 7272.727…,小数部分 ≥0.5,int() 版 727200 股、花费 9999000 元未超资金;round() 版 727300 股、花费 10000375 元,超出 1000 万元资金 375 元——说明按手取整数应使用 int()(向下截断才不会买超)。

    # 验证脚本:int与round取整手股数对比,以及round买超资金的自选价格演示
    fund = 1e7  # 投资资金额(元)
    N = 100  # 每手100股
    price_huatai = 13.76  # 华泰证券股价(元)
    print('int版股数:', N * int(fund / (price_huatai * N)))  # int向下截断商
    print('round版股数:', N * round(fund / (price_huatai * N)))  # round对商四舍五入
    price_demo = 13.75  # 自选价格:商的小数部分≥0.5
    lots_demo = fund / (price_demo * N)  # 计算商(可买手数)
    share_int_demo = N * int(lots_demo)  # int版整手股数
    share_round_demo = N * round(lots_demo)  # round版整手股数(多进位一手)
    print('int版:', share_int_demo, '股,花费', share_int_demo * price_demo, '元')  # int版花费
    print('round版:', share_round_demo, '股,花费', share_round_demo * price_demo, '元')  # round版花费
    print('round版是否买超资金:', share_round_demo * price_demo > fund)  # 判定是否超过资金

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    int版股数: 726700
    round版股数: 726700
    int版: 727200 股,花费 9999000.0 元
    round版: 727300 股,花费 10000375.0 元
    round版是否买超资金: True

    回扣主线章:同型整手买入任务见 列表 45.1 与第 章节 4 章。

  4. 变式任务(平台任务二、三同型改造):另找一组上市公司的年度净利润数据(不少于6家),用 sum()round() 计算总和与平均值,用 max()min() 找出最好与最差的公司,并用 sorted() 输出升序排名。

    参考答案(点开前请先独立完成)

    解题思路:取六家长三角上市公司 2023 年年报归母净利润(亿元,数据取自本地上市公司财务报表数据:交通银行 927.28、浦发银行 367.02、中国太保 272.57、宁波银行 255.35、海康威视 141.08、上海机场 9.34)。sum() 加总、round(sum/len, 2) 求平均;max()min()index() 定位公司名;升序排名用 sorted(zip(name_list, profit_2023)) 按元组首元素排序后即为按净利润升序的名次对。

    # 变式程序:六家长三角上市公司2023年年报归母净利润的汇总统计
    name_list = ['交通银行', '浦发银行', '中国太保', '宁波银行', '海康威视', '上海机场']  # 公司名单
    profit_2023 = [927.28, 367.02, 272.57, 255.35, 141.08, 9.34]  # 与名单一一对应的净利润(亿元)
    print('净利润总和(亿元):', round(sum(profit_2023), 2))  # 总和保留两位小数
    print('净利润平均数(亿元):', round(sum(profit_2023) / len(profit_2023), 2))  # 平均值保留两位小数
    print('最赚钱的公司:', name_list[profit_2023.index(max(profit_2023))], max(profit_2023))  # 最好业绩
    print('最不赚钱的公司:', name_list[profit_2023.index(min(profit_2023))], min(profit_2023))  # 最差业绩
    rank_pairs = sorted(zip(name_list, profit_2023), key=lambda pair: pair[1])  # 按净利润升序的(公司, 净利润)对
    print('升序排名:', rank_pairs)  # 输出升序排名

    预期输出(本机 peter 环境实际运行结果,具体以平台运行结果为准):

    净利润总和(亿元): 1972.64
    净利润平均数(亿元): 328.77
    最赚钱的公司: 交通银行 927.28
    最不赚钱的公司: 上海机场 9.34
    升序排名: [('上海机场', 9.34), ('海康威视', 141.08), ('宁波银行', 255.35), ('中国太保', 272.57), ('浦发银行', 367.02), ('交通银行', 927.28)]

    注意:以上为本题变式的独立代码;列表 45.1 对应平台任务的原始代码块仍须原样输入教学平台,不要用本变式替换。

    回扣主线章sum()max()min()sorted() 的金融应用写法见第 章节 4 章。